package stuttgart.uni.locationtracker;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
import android.telephony.TelephonyManager;
import android.util.Log;

public class LocationTrackerClient{

	private final String fileNameRealGps ="realGps.gpx";
	private final String fileNameEstimatedGps ="estimatedGps.gpx";
	private GpxDataSaver realGpsDataSaver;
	private GpxDataSaver estimatedGpsDataSaver;
	private final long UPDAETE_INTERVAL = 1000;
	
	private LocationTrackerService service;
	
	private LocationManager locationManager;
	private LocationListener locationListener;
	
	private TrackingData realGpsData;

	private long gpsTime;
	private double gpsLatitude;
	private double gpsLongitude;
	private float gpsBearing;
	private float gpsSpeed;
	
	private String serverIpAddress;
	private Socket tcpSocket;

	
	private int tcpPort = 50;
	private int udpPortNumber = 51;
	
	public static final int REQUEST_CLIENT_UPDATE = 0;
	public static final int REQUEST_FRIENDLIST_UPDATE = 1;
	public static final int REQUEST_LOGOUT = 2;
	
	private ObjectInputStream tcpIn;
	private ObjectOutputStream tcpOut;
	private DatagramSocket dSocket;
	private String username;
	private String id;
	
	private Predictor predictor;
	private double sendGpsPacketCount = 0d;
	private double totalGpsPacketCount = 0d;
	private double packetReductionRatio = 0d;

	public LocationTrackerClient(LocationTrackerService s,String ipAddress, String username) {
		
		service=s;
		this.serverIpAddress = ipAddress;
		this.username=username;
		predictor = new Predictor(new TrackingData("","",0l,0d,0d,0f,0f));
		
		TelephonyManager tm = (TelephonyManager) service.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
		id = tm.getDeviceId();
		
		//Build UDP Socket
		try {
			dSocket = new DatagramSocket();
		} catch (SocketException e) {
			Log.d("LocationTracker","SocketException beim erstellen des DatagramSocket");
		}
		
		try {
			dSocket.connect(InetAddress.getByName(ipAddress),udpPortNumber);
		} catch (UnknownHostException e) {
			Log.d("LocationTracker","SocketException beim connect des DatagramSocket");
		}
		
		//Add LocationListener
		
		locationManager = (LocationManager) service.getSystemService(LocationTrackerService.LOCATION_SERVICE);
		realGpsDataSaver = new GpxDataSaver(new File(service.getExternalFilesDir(null), fileNameRealGps));
		estimatedGpsDataSaver = new GpxDataSaver(new File(service.getExternalFilesDir(null), fileNameEstimatedGps));
		
    	locationListener = new LocationListener() {
			public void onLocationChanged(Location location) {
				handelGpsEvent(location);
			}
			public void onProviderDisabled(String provider) {
			}
			public void onProviderEnabled(String provider) {
			}
			public void onStatusChanged(String provider, int status, Bundle extras) {
			}
		};
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, UPDAETE_INTERVAL, 0, locationListener,Looper.myLooper());
	}

	public synchronized TrackingData getRealGpsData() {
		return realGpsData;
	}

	public synchronized void setRealGpsData(TrackingData realGpsData) {
		this.realGpsData = realGpsData;
	}

	private synchronized void handelGpsEvent(Location location) {
		Log.d("LogService","update data");
		
		//get information from Location Object
		gpsTime = location.getTime();
		gpsLatitude = location.getLatitude();
		gpsLongitude = location.getLongitude();
		gpsBearing = location.getBearing();
		gpsSpeed = location.getSpeed();
		
		totalGpsPacketCount = totalGpsPacketCount + 1d;
		
		setRealGpsData(new TrackingData(id,username,gpsTime,gpsLatitude,gpsLongitude,gpsBearing,gpsSpeed));
		
		if(!predictor.isInNextEpsilon(getRealGpsData(), UPDAETE_INTERVAL)){
			sendDataUpdateToServer(getRealGpsData());
			predictor = new Predictor(getRealGpsData());
			sendGpsPacketCount=sendGpsPacketCount+1d;
		}
		packetReductionRatio = (1d-(sendGpsPacketCount/totalGpsPacketCount));
		service.updateOwnValues(getRealGpsData());
		service.updatePacketReductionRatio(packetReductionRatio);
		estimatedGpsDataSaver.storePosition(predictor.getEstimatedPositionCurrently());
		realGpsDataSaver.storePosition(getRealGpsData());
	}
	
	private void sendDataUpdateToServer(TrackingData realGpsData){
		
		byte[] data = ObjectArrayConverter.toByteArrayfromObject(realGpsData);
		
		try {
			Log.d("LocationTrackerClient","Sending to: "+dSocket.getInetAddress().toString()+" Lenth:"+Integer.toString(data.length));
			dSocket.send(new DatagramPacket(data, data.length));
		} catch (IOException e) {
			Log.d("LocationTracker","IOException in sendDataUpdateToServer");
		}
	}
	
	public synchronized TrackingData getFriendUpdateFromServer(String id) {
		TrackingData result = null;
		
		openTcp();
		try {
			tcpOut.writeInt(REQUEST_CLIENT_UPDATE);
			tcpOut.flush();
			tcpOut.writeUTF(id);
			tcpOut.flush();
			result = (TrackingData) tcpIn.readObject();
		} catch (IOException e) {
			Log.d("LocationTrackerClient","IOException in getFriendUpdateFromServer()");
		} catch (ClassNotFoundException e) {
			Log.d("LocationTrackerClient","ClassNotFoundException in getFriendUpdateFromServer()");
		}
		closeTcp();

		return result;
	}

	public synchronized FriendList getFriendListFromServer() {
		FriendList result = null;

		openTcp();
		try {
			tcpOut.writeInt(REQUEST_FRIENDLIST_UPDATE);
			tcpOut.flush();
			result = (FriendList) tcpIn.readObject();
		} catch (IOException e) {
			Log.d("LocationTrackerClient","IOException in getFriendListFromServer()");
		} catch (ClassNotFoundException e) {
			Log.d("LocationTrackerClient","ClassNotFoundException in getFriendListFromServer()");
		}
		closeTcp();

		return result;
	}
	
	public synchronized void logoutFromServer(){
	
		openTcp();
	
		try {
			tcpOut.writeInt(REQUEST_LOGOUT);
			tcpOut.flush();
			tcpOut.writeUTF(id);
			tcpOut.flush();
		} catch (IOException e) {
			Log.d("LocationTrackerClient","IOException in logoutFromServer()");
		}
		closeTcp();
		Log.d("LocationTrackerClient","after close");
	}
	
	private void openTcp(){
		try {
			tcpSocket = new Socket(InetAddress.getByName(serverIpAddress),tcpPort);
			tcpIn = new ObjectInputStream(tcpSocket.getInputStream());
			tcpOut = new ObjectOutputStream(tcpSocket.getOutputStream());
		} catch (UnknownHostException e) {
			Log.d("LocationTrackerClient","UnknownHostException in openTcp()");
		} catch (IOException e) {
			Log.d("LocationTrackerClient","IOException in openTcp()");
		}
	}
	
	private void closeTcp(){
		if(tcpIn!=null){
			try {
				tcpIn.close();
			} catch (IOException e) {
				Log.d("LocationTrackerClient","could not close tcpIn");
			}		
		}
		
		if(tcpOut!=null){
			try {
				tcpOut.close();
			} catch (IOException e) {
				Log.d("LocationTrackerClient","could not close tcpOut");
			}		
		}
		
		if(tcpSocket!=null){
			try {
				tcpSocket.close();
			} catch (IOException e) {
				Log.d("LocationTrackerClient","could not close tcpSocket");
			}
		}
	}
	
	public void close() {
		Log.d("LocationTrackerClient","LocationTrackerClient closing");
		estimatedGpsDataSaver.close();
		realGpsDataSaver.close();
		locationManager.removeUpdates(locationListener);
		Log.d("LocationTrackerClient","location updates removed");
		dSocket.close();
	}
}
